﻿#region codejockey.net Copyright Banner

/***************************************************************\
 * Copyright 2010 codejockey.net.                                *
 *                                                               *
 * This code is property of codejockey.net and cannot be used    *
 * without explicit permission given by a codejockey.net         *
 * Representative.  For details contact: source@codejockey.net   *
 \***************************************************************/

#endregion

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Xml;

using codejockey.BingPaper.Utils.ReplacementTokens;
using codejockey.Shared.Web;

namespace codejockey.BingPaper.Utils
{
    public static class BingUtils
    {
        #region Default Values

        private const string BaseUrl = "http://www.bing.com";

        private const string RssUrlWithCountryCodeAndIndexParams =
          BaseUrl + "/HPImageArchive.aspx?format=xml&idx={0}&n=1&cc={1}";

        #endregion

        #region XML Nodes

        private const string ImageNodeName = "images/image";
        private const string MessagesNodeName = "images/image/messages/message";
        private const string MessageTextNodeName = "msgtext";
        private const string MessageTitleNodeName = "msgtitle";
        private const string MessageLinkNodeName = "msglink";
        private const string UrlNodeName = "url";
        private const string CopyrightNodeName = "copyright";

        private const string HotSpotNodeName = "images/image/hotspots/hotspot";
        private const string HotSpotDescriptionNodeName = "desc";
        private const string HotSpotLinkNodeName = "link";
        private const string HotSpotQueryNodeName = "query";
        private const string HotSpotLocXNodeName = "LocX";
        private const string HotSpotLocYNodeName = "LocY";

        #endregion

        #region Messages

        private const string UnableToFindImageNodeErrorMessage = "Unable to find Image node";
        private const string UnableToFindUrlNodeMessage = "Unable to find URL node";
        private const string UnableToContactWebServerMessage = "Unable to contact the web server.";

        #endregion

        #region Private Fields

        private static string _bingImageName = string.Empty;
        private static string _currentCountryCode = string.Empty;
        private static readonly CountryCodes _countryCodes = new CountryCodes();
        private static int CurrentDayIndex { get; set; }

        #endregion

        #region Constructors

        static BingUtils()
        {
            LoadTokens();
            AddCustomTokens();
        }

        #endregion

        #region Public Properties

        public static string CurrentCountryCode
        {
            get
            {
                if (string.IsNullOrEmpty(_currentCountryCode))
                    _currentCountryCode = _countryCodes[CountryCodes.DefaultCountryCodeKey];

                return _currentCountryCode;
            }
            set { _currentCountryCode = value; }
        }

        public static string CurrentCountryCodeKey
        {
            get
            {
                var result = string.Empty;

                if (string.IsNullOrWhiteSpace(_currentCountryCode))
                    _currentCountryCode = _countryCodes[CountryCodes.DefaultCountryCodeKey];

                if (_countryCodes != null)
                    foreach (var curItem in _countryCodes.Where(curItem => curItem.Value == _currentCountryCode))
                    {
                        result = curItem.Key;
                        break;
                    }

                return result;
            }
        }

        public static CountryCodes CountryCodes
        {
            get { return _countryCodes; }
        }

        #endregion

        #region Private Methods

        private static void AddCustomTokens()
        {
            var bingImageToken = new BingImageNameReplacementToken(ScrubBingString(GetBingImageName()));
            var countryCodeToken = new CountryCodeReplacementToken();

            if (!TokenManager.Tokens.ContainsKey(bingImageToken.Token))
                TokenManager.Tokens.Add(bingImageToken.Token, bingImageToken);

            if (!TokenManager.Tokens.ContainsKey(countryCodeToken.Token))
                TokenManager.Tokens.Add(countryCodeToken.Token, countryCodeToken);
        }

        #endregion

        public static void LoadTokens()
        {
            TokenManager.LoadTokens();
            AddCustomTokens();
        }

        public static BingImage GetBingImage(BingImageParams bingImageParams)
        {
            var imageDetails = new BingImage();

            if (bingImageParams != null)
            {
                if (BingImageParams.Validate())
                {
                    var request = (HttpWebRequest)WebRequest.Create(bingImageParams.Uri);
                    var response = (HttpWebResponse)request.GetResponse();
                    Stream responseStream = response.GetResponseStream();
                    var doc = new XmlDocument();

                    if (responseStream != null)
                        doc.Load(responseStream);

                    //Grab the frist node out of the list, as currently the RSS only contains one image.      
                    XmlNode imageNode = doc.SelectSingleNode(ImageNodeName);
                    if (imageNode == null)
                        throw new Exception(UnableToFindImageNodeErrorMessage);

                    XmlNode urlNode = imageNode.SelectSingleNode(UrlNodeName);

                    if (urlNode != null)
                    {
                        string fileName = ScrubBingString(urlNode.InnerText);
                        imageDetails.ImageName = fileName;
                    }

                    var selectSingleNode = imageNode.SelectSingleNode(CopyrightNodeName);
                    if (selectSingleNode != null)
                        imageDetails.Copyright = selectSingleNode.InnerText;

                    //imageNode.SelectSingleNode(CopyrightNodeName).InnerText = imageDetails.Copyright;

                    if (urlNode != null)
                        imageDetails.ImageUrl = new Uri(string.Format("{0}{1}", BaseUrl, urlNode.InnerText));

                    imageDetails.CurrentImage = GetCurrentImage(imageDetails.ImageUrl, bingImageParams.Path, bingImageParams.File);

                    if (!string.IsNullOrEmpty(bingImageParams.Path) && !string.IsNullOrEmpty(bingImageParams.File))
                        imageDetails.ImageFileName = Path.Combine(TokenManager.Parse(bingImageParams.Path),
                                                                  TokenManager.Parse(bingImageParams.File));

                    imageDetails.HotSpots = GetHotspots(doc);
                    imageDetails.Messsages = GetMessages(doc);

                    if (!string.IsNullOrEmpty(bingImageParams.CountryCode))
                        CurrentCountryCode = bingImageParams.CountryCode;

                    CurrentDayIndex = bingImageParams.DayIndex;
                }
            }
            return imageDetails;
        }

        public static BingImage GetBingImage(string countryCode, int dayIndex = 0)
        {
            var bingParams = new BingImageParams { CountryCode = countryCode, DayIndex = dayIndex };

            return GetBingImage(bingParams);
        }

        private static Image GetCurrentImage(Uri uri, string path, string fileName)
        {
            bool temp = false;

            if (string.IsNullOrEmpty(path))
            {
                path = Path.GetTempPath();
                temp = true;
            }

            if (string.IsNullOrEmpty(fileName))
            {
                fileName = Path.GetTempFileName();
                temp = true;
            }

            string tempImage = Path.Combine(TokenManager.Parse(path), TokenManager.Parse(fileName));

            CreateFilePath(TokenManager.Parse(path));
            Image result = null;

            WebUtils.GetFile(uri, tempImage);

            using (Stream s = new WebClient().OpenRead(tempImage))
            {
                if (s != null)
                {
                    result = Image.FromStream(s);
                    s.Flush();
                    s.Close();
                }
            }

            if (temp)
                File.Delete(tempImage);

            return result;
        }

        private static List<Message> GetMessages(XmlDocument doc)
        {
            // Grab the messages node.
            var messages = doc.SelectNodes(MessagesNodeName);
            var bingMessages = new List<Message>();

            if (messages != null)
            {
                for (int i = 0; i < messages.Count; i++)
                {
                    var message = new Message
                                    {
                                        Text = messages[i].SelectSingleNode(MessageTextNodeName).InnerText,
                                        Title = messages[i].SelectSingleNode(MessageTitleNodeName).InnerText,
                                        Link = new Uri(messages[i].SelectSingleNode(MessageLinkNodeName).InnerText)
                                    };

                    bingMessages.Add(message);
                }
            }

            return bingMessages;
        }

        private static List<HotSpot> GetHotspots(XmlDocument doc)
        {
            // Grab the hotspots node.
            var hotSpots = doc.SelectNodes(HotSpotNodeName);
            var bingHotSpots = new List<HotSpot>();

            if (hotSpots != null)
            {
                for (int i = 0; i < hotSpots.Count; i++)
                {
                    var hotSpot = new HotSpot
                                    {
                                        Description = hotSpots[i].SelectSingleNode(HotSpotDescriptionNodeName).InnerText,
                                        Link = new Uri(hotSpots[i].SelectSingleNode(HotSpotLinkNodeName).InnerText),
                                        Query = hotSpots[i].SelectSingleNode(HotSpotQueryNodeName).InnerText
                                    };

                    int locX;
                    hotSpot.LocationX = !int.TryParse(hotSpots[i].SelectSingleNode(HotSpotLocXNodeName).InnerText, out locX) ? 0 : locX;

                    int locY;
                    hotSpot.LocationY = !int.TryParse(hotSpots[i].SelectSingleNode(HotSpotLocYNodeName).InnerText, out locY) ? 0 : locY;

                    bingHotSpots.Add(hotSpot);
                }
            }

            return bingHotSpots;
        }

        private static string GetBingImageName()
        {
            string rssUrl = string.Format(RssUrlWithCountryCodeAndIndexParams, CurrentDayIndex, CurrentCountryCode);

            var request = (HttpWebRequest)WebRequest.Create(rssUrl);
            HttpWebResponse response;
            try
            {
                response = (HttpWebResponse)request.GetResponse();
            }
            catch
            {
                throw new Exception(UnableToContactWebServerMessage);
            }
            Stream responseStream = response.GetResponseStream();
            var doc = new XmlDocument();

            if (responseStream != null)
                doc.Load(responseStream);

            //Grab the frist node out of the list, as currently the RSS only 
            //  contains one image.      
            XmlNode imageNode = doc.SelectSingleNode(ImageNodeName);
            if (imageNode == null)
                throw new Exception(UnableToFindImageNodeErrorMessage);

            XmlNode urlNode = imageNode.SelectSingleNode(UrlNodeName);

            if (urlNode == null)
                throw new Exception(UnableToFindUrlNodeMessage);

            string nodeText = urlNode.InnerText;

            return nodeText;
        }

        private static string ScrubBingString(string sourceString)
        {
            int index = sourceString.LastIndexOf("/");
            _bingImageName = sourceString.Substring(index + 1);
            _bingImageName = WebUtility.HtmlDecode(sourceString);
            _bingImageName = _bingImageName.Replace(".jpg", "");
            index = _bingImageName.IndexOf("%2f");
            _bingImageName = _bingImageName.Substring(index + 3);

            return _bingImageName;
        }

        /// <summary>
        /// Parses the name of the tokens from path.
        /// </summary>
        /// <param name="tokenizedString">The path to save file to.</param>
        /// <returns>A string with known tokens replaced.</returns>
        public static string ParseTokens(string tokenizedString)
        {
            return TokenManager.Parse(tokenizedString);
        }

        private static void CreateFilePath(string pathToSaveFileTo)
        {
            if (!Directory.Exists(pathToSaveFileTo))
                Directory.CreateDirectory(pathToSaveFileTo);
        }
    }
}